package com.zeyu.framework.tools.console;

import com.google.common.collect.Maps;
import com.zeyu.framework.modules.sys.utils.UserUtils;
import com.zeyu.framework.tools.console.entity.BasicConfiguration;
import com.zeyu.framework.tools.console.service.ConsoleParamService;
import com.zeyu.framework.tools.console.stream.StreamInterceptingTunnel;
import com.zeyu.framework.utils.SpringContextHolder;
import com.zeyu.framework.utils.StringUtils;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleUnauthorizedException;
import org.apache.guacamole.net.GuacamoleSocket;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.InetGuacamoleSocket;
import org.apache.guacamole.net.SSLGuacamoleSocket;
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * Utility class that takes a standard request from the Guacamole JavaScript
 * client and produces the corresponding GuacamoleTunnel. The implementation
 * of this utility is specific to the form of request used by the upstream
 * Guacamole web application, and is not necessarily useful to applications
 * that use purely the Guacamole API.
 * Created by zeyuphoenix on 16/10/17.
 */
@Service
public class TunnelRequestService implements EnvironmentAware {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);


    /**
     * The hostname to use when connecting to guacd if no hostname is provided
     * within guacamole.properties.
     */
    private static final String DEFAULT_CONSOLE_HOSTNAME = "localhost";

    /**
     * The port to use when connecting to guacd if no port is provided within
     * guacamole.properties.
     */
    private static final String DEFAULT_CONSOLE_PORT = "4822";

    /**
     * The port to use when connecting to guacd if no port is provided within
     * guacamole.properties.
     */
    private static final String DEFAULT_CONSOLE_SSL = "false";

    // ================================================================
    // Fields
    // ================================================================

    /**
     * guacd 主机IP
     */
    private String hostname;
    /**
     * guacd 端口号
     */
    private int port;
    /**
     * guacd 是否启用ssl
     */
    private boolean ssl;

    /**
     * 保持连接信息
     */
    private Map<String, StreamInterceptingTunnel> tunnelMap = Maps.newConcurrentMap();

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    @Override
    public void setEnvironment(Environment environment) {
        this.hostname = environment.getProperty("framework.console.hostname", DEFAULT_CONSOLE_HOSTNAME);
        this.port = Integer.valueOf(environment.getProperty("framework.console.port", DEFAULT_CONSOLE_PORT));
        this.ssl = Boolean.valueOf(environment.getProperty("framework.console.ssl", DEFAULT_CONSOLE_SSL));
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * Reads and returns the client information provided within the given
     * request.
     *
     * @param request
     *     The request describing tunnel to create.
     *
     * @return GuacamoleClientInformation
     *     An object containing information about the client sending the tunnel
     *     request.
     *
     * @throws GuacamoleException
     *     If the parameters of the tunnel request are invalid.
     */
    protected GuacamoleClientInformation getClientInformation(TunnelRequest request)
            throws GuacamoleException {

        // Get client information
        GuacamoleClientInformation info = new GuacamoleClientInformation();

        // Set width if provided
        Integer width = request.getWidth();
        if (width != null)
            info.setOptimalScreenWidth(width);

        // Set height if provided
        Integer height = request.getHeight();
        if (height != null)
            info.setOptimalScreenHeight(height);

        // Set resolution if provided
        Integer dpi = request.getDPI();
        if (dpi != null)
            info.setOptimalResolution(dpi);

        // Add audio mimetypes
        List<String> audioMimetypes = request.getAudioMimetypes();
        if (audioMimetypes != null)
            info.getAudioMimetypes().addAll(audioMimetypes);

        // Add video mimetypes
        List<String> videoMimetypes = request.getVideoMimetypes();
        if (videoMimetypes != null)
            info.getVideoMimetypes().addAll(videoMimetypes);

        // Add image mimetypes
        List<String> imageMimetypes = request.getImageMimetypes();
        if (imageMimetypes != null)
            info.getImageMimetypes().addAll(imageMimetypes);

        return info;
    }

    /**
     * Creates a new tunnel using the parameters and credentials present in
     * the given request.
     *
     * @param request
     *     The request describing the tunnel to create.
     *
     * @return
     *     The created tunnel, or null if the tunnel could not be created.
     *
     * @throws GuacamoleException
     *     If an error occurs while creating the tunnel.
     */
    public GuacamoleTunnel createTunnel(TunnelRequest request)
            throws GuacamoleException {

        // Parse request parameters
        GuacamoleClientInformation info = getClientInformation(request);

        try {

            String id = request.getParameter("ID");
            if (id != null) {
                ConsoleParamService consoleParamService = SpringContextHolder.getBean(ConsoleParamService.class);
                BasicConfiguration configuration = consoleParamService.findByConnId(id);
                if (configuration != null) {
                    // VNC
                    // RDP
                    // SSH
                    // Telnet
                    // 转换格式
                    GuacamoleConfiguration config = transConfiguration(configuration);

                    // Create connected tunnel using client information and configuration
                    GuacamoleTunnel tunnel = connect(info, config);
                    logger.info("connect to tunnel used info : {}", configuration);

                    return tunnel;
                }
            }

            // ssh
            /* SSHConfiguration sshConfiguration = new SSHConfiguration();
            sshConfiguration.setHostname("192.168.128.130");
            sshConfiguration.setPort(22);
            sshConfiguration.setUsername("root");
            sshConfiguration.setPassword("123456");
            sshConfiguration.setFont_name("Courier New");
            sshConfiguration.setFont_size(15); */

            return null;

        }
        // Ensure any associated session is invalidated if unauthorized
        catch (Exception e) {

            logger.error("connect to tunnel error: ", e);
            // Continue with exception processing
            throw e;

        }

    }

    public GuacamoleTunnel connect(GuacamoleClientInformation info, GuacamoleConfiguration config)
            throws GuacamoleException {

        // Get guacd connection parameters

        GuacamoleSocket socket;

        // If guacd requires SSL, use it
        if (this.ssl)
            socket = new ConfiguredGuacamoleSocket(
                    new SSLGuacamoleSocket(this.hostname, this.port),
                    config, info
            );

            // Otherwise, just connect directly via TCP
        else
            socket = new ConfiguredGuacamoleSocket(
                    new InetGuacamoleSocket(this.hostname, this.port),
                    config, info
            );

        return createAssociatedTunnel(new SimpleGuacamoleTunnel(socket));

    }


    protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel) throws GuacamoleException {

        // Monitor tunnel closure and data
        StreamInterceptingTunnel monitoredTunnel = new StreamInterceptingTunnel(tunnel) {

            /**
             * The time the connection began, measured in milliseconds since
             * midnight, January 1, 1970 UTC.
             */
            private final long connectionStartTime = System.currentTimeMillis();

            @Override
            public void close() throws GuacamoleException {

                long connectionEndTime = System.currentTimeMillis();
                long duration = connectionEndTime - connectionStartTime;

                logger.info("User \"{}\" disconnected from connection. Duration: {} milliseconds",
                        UserUtils.getUser(), duration);

                try {

                    // Close and clean up tunnel
                    tunnelMap.remove(getUUID().toString());
                    super.close();

                }

                // Ensure any associated session is invalidated if unauthorized
                catch (GuacamoleUnauthorizedException e) {

                    // Continue with exception processing
                    throw e;

                }

            }

        };

        // Associate tunnel with session
        tunnelMap.put(monitoredTunnel.getUUID().toString(), monitoredTunnel);
        return monitoredTunnel;

    }

    /**
     * 通过id获取tunnel
     */
    public StreamInterceptingTunnel getTunnelById(String uuid) {
        return tunnelMap.get(uuid);
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    /**
     * 将界面参数转换为console配置参数
     */
    private GuacamoleConfiguration transConfiguration(BasicConfiguration basicConfiguration) {
        GuacamoleConfiguration configuration = new GuacamoleConfiguration();

        if (basicConfiguration == null) {
            logger.warn("html configuration is null, please check.");
            return configuration;
        }

        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(basicConfiguration.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            // 设置参数
            configuration.setProtocol(basicConfiguration.getProtocol());
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();

                // 过滤class属性
                if (!key.equals("class")) {
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(basicConfiguration);

                    // String boolean int
                    if (getter.getReturnType().equals(String.class)) {
                        if (value == null || StringUtils.isEmpty(value.toString())) {
                            continue;
                        }
                    } else if (getter.getReturnType().equals(int.class)) {
                        if (value == null || Integer.valueOf(value.toString()) < 0) {
                            continue;
                        }
                    } else if (getter.getReturnType().equals(boolean.class)) {
                        if (value == null || !Boolean.valueOf(value.toString())) {
                            continue;
                        }
                    } else {
                        logger.debug("unknown return type : {} ", getter.getReturnType());
                    }
                    if (StringUtils.equals("protocol", key)) {
                        continue;
                    }
                    configuration.setParameter(transKey(key), value.toString());
                }
            }
        } catch (Exception e) {
            logger.error("transBean2Map Error ", e);
        }

        return configuration;
    }

    /**
     * 修改配置中的key的-为_,字母小写
     */
    private String transKey(String key) {
        return key.replace("_", "-").toLowerCase();
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
